﻿/*
 * Managed ESL Suite
 * 
 * Version: MPL 1.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 * 
 * The Initial Developer of the Original Code is
 * Diego Toro <dftoro@yahoo.com>
 * Portions created by the Initial Developer are Copyright (C)
 * the Initial Developer. All Rights Reserved.
 * 
 * Contributor: 
 * 
 */

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;

namespace Managed.Esl.Suite
{
  public delegate void ChannelEventHandler(Object sender, ChannelEvent channelEvent);
  public delegate void EventHandler(Object sender, EventBase eventBase);

  public partial class ESLSession
  {
    #region Field

    private string stUniqueId;
    private ESLconnection esConnection;
    private EventBase info;
    private bool boInfoChanged;
    private ESLevent esEventResult;
    private EESLMode enESLMode;

    #endregion Field

    #region Property

    public EventBase Info
    {
      get { return LoadInfo(); }
    }

    public string UniqueId
    {
      get { return stUniqueId; }
    }

    public ESLconnection EslConnection
    {
      get { return this.esConnection; }
    }

    public bool IsConnected
    {
      get { return (this.esConnection.Connected() == ESLConstans.ESL_SUCCESS); }
    }

    public EESLMode ESLMode
    {
      get { return enESLMode; }
    }

    #endregion Property

    #region Constructor

    public ESLSession(string host, int port, string password, string subcribeEvent)
    {
      this.esConnection = new ESLconnection(host, port.ToString(), password);

      if (this.esConnection == null)
      {
        throw new Exception("Error creating ESL connection");
      }

      if (!this.IsConnected)
      {
        throw new Exception("Error connecting to FreeSWITCH server");
      }

      boInfoChanged = true;
      this.esConnection.SendRecv(subcribeEvent);
      this.enESLMode = EESLMode.Inbound;

      ThreadPool.QueueUserWorkItem((o) =>
      {
        ReceiveEvent();
      });

    }

    public ESLSession(int socketHandler, EESLMode enEslMode)
    {
      this.esConnection = new ESLconnection(socketHandler);

      if (this.esConnection == null)
      {
        throw new Exception("Error creating ESL connection");
      }

      if (!this.IsConnected)
      {
        throw new Exception("Error connecting to FreeSWITCH server");
      }

      boInfoChanged = true;
      this.info = new EventBase(this.esConnection.GetInfo());
      this.stUniqueId = info.GetHeader("unique-id");

      this.esConnection.SendRecv("myevents");
      this.esConnection.SendRecv("divert_events on");

      this.enESLMode = enEslMode;

      if (this.enESLMode == EESLMode.AsynOutbound)
      {
        ThreadPool.QueueUserWorkItem((o) =>
        {
          ReceiveChannelEvent();
        });
      }
    }

    #endregion Constructor

    #region Event

    public event ChannelEventHandler OnEventChannelReceive;
    public event EventHandler OnEventReceive;

    #endregion Event

    #region Indexer

    public string this[string varName]
    {
      get { return GetVariable(varName); }
      set { SetVariable(varName, value); }
    }

    #endregion Indexer

    #region Public Member

    public void SendInfo()
    {

    }

    public void SendNotity()
    {

    }

    public ChannelEvent PreAnswer()
    {
      boInfoChanged = true;

      switch (enESLMode)
      {
        case EESLMode.Inbound:
        case EESLMode.AsynOutbound:
          return new ChannelEvent(this.esConnection.Execute("pre_answer", null, stUniqueId));
        default:
          this.esConnection.Execute("pre_answer", null, stUniqueId);
          return WaiExecuteComplete();
      }
    }

    public ChannelEvent Answer()
    {
      boInfoChanged = true;

      switch (enESLMode)
      {
        case EESLMode.Inbound:
        case EESLMode.AsynOutbound:
          return new ChannelEvent(this.esConnection.Execute("answer", null, stUniqueId));
        default:
          this.esConnection.Execute("answer", null, stUniqueId);
          return WaiExecuteComplete();
      }
    }

    public ChannelEvent Bridge(string targetEndPoint)
    {
      boInfoChanged = true;

      switch (enESLMode)
      {
        case EESLMode.Inbound:
          return new ChannelEvent(this.esConnection.Execute("bridge", targetEndPoint, stUniqueId));
        case EESLMode.AsynOutbound:
          ChannelEvent chEvent;
          this.esConnection.SetEventLock("1");
          chEvent = new ChannelEvent(this.esConnection.Execute("bridge", targetEndPoint, stUniqueId));
          this.esConnection.SetEventLock("0");
          return chEvent;
        default:
          this.esConnection.Execute("bridge", targetEndPoint, stUniqueId);

          do
          {
            if (!IsConnected)
            {
              break;
            }

            esEventResult = this.EslConnection.RecvEvent();

            if (OnEventChannelReceive != null && esEventResult != null)
            {
              ThreadPool.QueueUserWorkItem((o) =>
              {
                var OnEventChannelReceiveLocal = OnEventChannelReceive;
                OnEventChannelReceiveLocal(this, new ChannelEvent(esEventResult));
              });
            }
          } while (esEventResult != null &&
            !esEventResult.getType().Equals("CHANNEL_EXECUTE_COMPLETE") &&
            !esEventResult.getType().Equals("CHANNEL_UNBRIDGE") &&
            !esEventResult.getType().Equals("SERVER_DISCONNECTED") &&
            !esEventResult.getType().Equals("CHANNEL_HANGUP"));

          return new ChannelEvent(esEventResult);
      }
    }

    public ChannelEvent Hangup()
    {
      boInfoChanged = true;

      switch (enESLMode)
      {
        case EESLMode.Inbound:
        case EESLMode.AsynOutbound:
          return new ChannelEvent(this.esConnection.Execute("hangup", null, stUniqueId));
        default:
          this.esConnection.Execute("hangup", null, stUniqueId);
          return WaiExecuteComplete();
      }
    }

    public ChannelEvent Hangup(string reason)
    {
      boInfoChanged = true;
      switch (enESLMode)
      {
        case EESLMode.Inbound:
        case EESLMode.AsynOutbound:
          return new ChannelEvent(this.esConnection.Execute("hangup", reason, stUniqueId));
        default:
          this.esConnection.Execute("hangup", reason, stUniqueId);
          return WaiExecuteComplete();
      }
    }

    public ChannelEvent Transfer(string destination)
    {
      boInfoChanged = true;

      switch (enESLMode)
      {
        case EESLMode.Inbound:
        case EESLMode.AsynOutbound:
          return new ChannelEvent(this.esConnection.Execute("transfer", destination, stUniqueId));
        default:
          this.esConnection.Execute("transfer", destination, stUniqueId);
          return WaiExecuteComplete();
      }
    }

    public ChannelEvent Playback(string data)
    {
      boInfoChanged = true;

      switch (enESLMode)
      {
        case EESLMode.Inbound:
        case EESLMode.AsynOutbound:
          return new ChannelEvent(this.esConnection.Execute("playback", data, stUniqueId));
        default:
          this.esConnection.Execute("playback", data, stUniqueId);
          return WaiExecuteComplete();
      }
    }

    public ChannelEvent PlayAndGetDigits
      (
      int min,
      int max,
      int tries,
      int timeout,
      string terminators,
      string file,
      string invalidFile,
      string varName,
      string regexp
      )
    {
      boInfoChanged = true;
      string data = String.Format("{0} {1} {2} {3} {4} {5} {6} {7} {8}", min, max, tries, timeout, terminators, file, invalidFile, varName, regexp);

      switch (enESLMode)
      {
        case EESLMode.Inbound:
        case EESLMode.AsynOutbound:
          return new ChannelEvent(this.esConnection.Execute("play_and_get_digits", data, stUniqueId));
        default:
          this.esConnection.Execute("play_and_get_digits", data, stUniqueId);
          return WaiExecuteComplete();
      }
    }

    public ChannelEvent Read
    (
    int min,
    int max,
    string file,
    string varName,
    int timeout,
    string terminators
    )
    {
      boInfoChanged = true;
      string data = String.Format("{0} {1} {2} {3} {4} {5}", min, max, file, varName, timeout, terminators);

      switch (enESLMode)
      {
        case EESLMode.Inbound:
        case EESLMode.AsynOutbound:
          return new ChannelEvent(this.esConnection.Execute("read", data, stUniqueId));
        default:
          this.esConnection.Execute("read", data, stUniqueId);
          return WaiExecuteComplete();
      }
    }

    public ChannelEvent Sleep(int pauseMilliseconds)
    {
      switch (enESLMode)
      {
        case EESLMode.Inbound:
        case EESLMode.AsynOutbound:
          return new ChannelEvent(this.esConnection.Execute("sleep", pauseMilliseconds.ToString(), stUniqueId));
        default:
          this.esConnection.Execute("sleep", pauseMilliseconds.ToString(), stUniqueId);
          return WaiExecuteComplete();
      }
    }

    public ChannelEvent SetVariable(string varName, string value)
    {
      switch (enESLMode)
      {
        case EESLMode.Inbound:
        case EESLMode.AsynOutbound:
          return new ChannelEvent(this.esConnection.Execute("set", varName + "=" + value, stUniqueId));
        default:
          this.esConnection.Execute("set", varName + "=" + value, stUniqueId);
          return WaiExecuteComplete();
      }
    }

    public string GetVariable(string varName)
    {
      LoadInfo();
      string stVar = this.info.GetHeader(varName);
      return stVar == null ? this.info.GetHeader("variable_" + varName) : stVar;
    }

    public ChannelEvent Execute(string app, string arg)
    {
      boInfoChanged = true;

      switch (enESLMode)
      {
        case EESLMode.Inbound:
        case EESLMode.AsynOutbound:
          return new ChannelEvent(this.esConnection.Execute(app, arg, stUniqueId));
        default:
          this.esConnection.Execute(app, arg, stUniqueId);
          return WaiExecuteComplete();
      }
    }

    public int Disconnect()
    {
      return this.esConnection.Disconnect();
    }

    #endregion Public Member

    #region Private Member

    private EventBase LoadInfo()
    {
      if (boInfoChanged)
      {
        boInfoChanged = false;

        switch (enESLMode)
        {
          case EESLMode.Inbound:
          case EESLMode.AsynOutbound:
            return new EventBase(this.esConnection.Execute("info", null, stUniqueId));
          default:
            this.esConnection.Execute("info", null, stUniqueId);
            info = WaiExecuteComplete();
            break;
        }
      }

      return info;
    }

    private ChannelEvent WaiExecuteComplete()
    {
      do
      {
        if (!IsConnected)
        {
          break;
        }

        esEventResult = this.EslConnection.RecvEvent();

        if (OnEventChannelReceive != null && esEventResult != null)
        {
          ThreadPool.QueueUserWorkItem((o) =>
          {
            var OnEventChannelReceiveLocal = OnEventChannelReceive;
            OnEventChannelReceiveLocal(this, new ChannelEvent(esEventResult));
          });
        }
      } while (esEventResult != null &&
        !esEventResult.getType().Equals("CHANNEL_EXECUTE_COMPLETE") &&
        !esEventResult.getType().Equals("SERVER_DISCONNECTED") &&
        !esEventResult.getType().Equals("CHANNEL_HANGUP"));

      return new ChannelEvent(esEventResult);
    }

    private void ReceiveChannelEvent()
    {
      while (IsConnected)
      {
        esEventResult = this.EslConnection.RecvEventTimed(200);

        if (OnEventChannelReceive != null && esEventResult != null)
        {
          var OnEventChannelReceiveLocal = OnEventChannelReceive;
          OnEventChannelReceiveLocal(this, new ChannelEvent(esEventResult));
          OnEventChannelReceiveLocal(this, new ChannelEvent(esEventResult));
        }
      }
    }

    private void ReceiveEvent()
    {
      while (IsConnected)
      {
        esEventResult = this.EslConnection.RecvEventTimed(200);

        if (OnEventChannelReceive != null && esEventResult != null)
        {
          var OnEventChannelReceiveLocal = OnEventChannelReceive;
          OnEventReceive(this, new EventBase(esEventResult));
        }
      }
    }

    #endregion Private Member

  }
}
